#include <unistd.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <signal.h>
#include "window.h"
#include "client.h"
#include "utility.h"
#include "debug.h"
#include <pthread.h>
#include <map>
#include <stdio.h>
#include <string>
#include "BinarySearchTree.h"
using std::string;
using std::map;
/** jump buffer evniroment*/
jmp_buf env;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

/** Sigint handler doy-ee*/
void sigint_handler( int i ) {
   longjmp( env, 1 );
}

/** setups up the tcp client and attemps to connect to hostname and port */
int tcp_setup_client(const char* hostname, const char* port) {
   int sk;
   struct sockaddr_in remote;
   struct hostent *hp;

   if( (sk = socket(AF_INET, SOCK_STREAM,0)) == -1 ) {
      return -1;
   }
   remote.sin_family = AF_INET;
   if((hp = gethostbyname(hostname)) == NULL ) {
      return -1;
   }
   memcpy( (char*) &remote.sin_addr, (char*)hp->h_addr, hp->h_length);
   remote.sin_port = htons( atoi(port) );
   if( connect( sk, (struct sockaddr*)&remote, sizeof( struct sockaddr_in))
         == -1 ) {
      return -1;
   }
   return sk;
}
/**
 * sets up windows and attempts a connection
 */
STATES setup( ChatWindows &cw, int &sk, const char* host, const char* port ) {
   struct sigaction sa;
   sa.sa_handler = sigint_handler;
   sigemptyset(&sa.sa_mask);
   sa.sa_flags = 0;
   if( sigaction(SIGINT, &sa, NULL) == -1) {
      return ERROR;
   }

   SetupWindows(cw);
   if( (sk = tcp_setup_client(host, port)) == -1 ) {
      return ERROR;
   }
   return LOGIN;
}
/**
 * prompts user for login, sends request
 * if request is denied refuse
 * else if accepted goto SELECT
 * else if error goto ERROR
 */
STATES login( ChatWindows &cw, int sk) {
   char uname[UNAME_SIZE];
   FCALL( echo() == ERR, "Failed to enable echo");
   FCALL( wprintw( cw.wb, "Enter Username: ") == ERR, "failed to print");
   FCALL( wgetnstr( cw.wb, uname, UNAME_SIZE -1 )
         == ERR, "Failed to get string");
   FCALL( noecho() == ERR, "Failed to disable echo");
   uname[UNAME_SIZE -1] = '\0';
   if( send_mesg( sk, NOTICE, N_ULOGIN, strlen(uname) + 1, 0, uname ) == -1) {
      return ERROR;
   }
   return SELECT;
}
/**
 * waits for input on either stdin or the socket
 */
STATES wait_on_select(int sk, Message &m ) {
   int status;
   if( (status = select_call( sk, SELECT_SEC, SELECT_MSEC )) == -1 ) {
      return ERROR;
   }
   else if ( status == 0 ) {
      return SELECT;
   }

   if( recv_mesg( sk, m ) == -1 ) {
      return ERROR;
   }

   return UPDATE;
}
/**
 * Recieved user input,
 * if enter send a message
 * else if special input activate window
 * else handle the character
 * goto SELECT
 */
void * get_input( void * v ) {
   const char* str;
   int ch, status;
   ChatWindows & cw = *(((thread_param*)v)->cw);
   int sk = ((thread_param*)v)->sk;

   LOCK();  //Dont wanna start the loop before full setup
   UNLOCK();
   while( true ) {
      if( select_call( STDIN_FILENO,SELECT_SEC,SELECT_MSEC ) != 0 ) {
         if( (ch = getch()) != ERR ) {
            LOCK();
            if( (status = Activate(cw,ch) ) ) {
            
            }
            UNLOCK();
            if ( ! status ) {
               if( (str = GetString(cw, ch)) != NULL ) {
                  DMSG1("Sending Message: %s", str );
                  if( send_mesg( sk, BMSG, 0, strlen(str), 0,str) == -1) {
                     perror("Thread");
                     break;
                  }
               }

            }
            LOCK();
            refreshCDKScreen( cw.sc );
            UNLOCK();
            FCALL( wrefresh(cw.wb) == ERR, "Failed to refresh");
         }
      }
      else {
         LOCK();
         RefreshAll( cw );
         UNLOCK();
      }
   }
   pthread_exit((void*)EXIT_SUCCESS);
   return NULL;
}

/**
 * received a message from the server
 * add to screen and goto SELECT
 */
STATES new_message( const Message &m, ChatWindows &cw) {
   static BinarySearchTree<int, string> ustr;
   static char buf[512];
   if( m.type == NOTICE ) {
      DMSG("Received Notice");
      if( m.flags == N_REFUSED ) {
         DMSG("Noticed: Refused");
         LOCK();
         addCDKSwindow(cw.sw,(char*)m.message, BOTTOM);
         UNLOCK();
         return REFUSED;
      }
      if( m.flags == N_ULOGIN || m.flags == N_OLOGIN ) {
         ustr[m.sender] = m.message;
         sprintf( buf, "%s has logged in.",ustr[m.sender].c_str());
         LOCK();
         addCDKSwindow(cw.sw,buf, BOTTOM);
         UNLOCK();
         return SELECT;
      }
      if( m.flags == N_ULOGOUT || m.flags == N_OLOGOUT ) {
         sprintf( buf, "%s has logged out.",ustr[m.sender].c_str());
         LOCK();
         addCDKSwindow(cw.sw,buf, BOTTOM);
         UNLOCK();
         ustr[m.sender] = m.message;
         return SELECT;
      }
   }
   else if ( m.type == BMSG ) {
      DMSG1("Received broadcast message: %s", m.message);
      LOCK();
      sprintf( buf, "%s: %s", ustr[m.sender].c_str(),(char*) m.message );
      addCDKSwindow(cw.sw,buf, BOTTOM);
      UNLOCK();
      return SELECT;
   }
   DMSG1("Noticed: %u", m.type );
   DMSG("default returning Error");
   return ERROR;
}
/** the login was refused
 * goto exit
 */
STATES refused() {
   fprintf(stderr, "Login Failed\n");
   return EXIT;
}

